// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

//  Internal.Reflection.Augments
//  -------------------------------------------------
//  Why does this exist?:
//    Also, IntrospectionServices.GetTypeInfo() and Assembly.Load()
//    are defined in System.Reflection but need a way to "call into"
//    Reflection.Core.dll to do the real work.
//
//    This contract adds the additional entrypoints needed to System.Reflection.
//
//  Implemented by:
//    System.Reflection.dll on RH (may use ILMerging instead)
//
//  Consumed by:
//    Reflection.Core.dll

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using System.Reflection.Runtime.Assemblies;
using System.Reflection.Runtime.BindingFlagSupport;
using System.Reflection.Runtime.FieldInfos;
using System.Reflection.Runtime.FieldInfos.NativeFormat;
using System.Reflection.Runtime.General;
using System.Reflection.Runtime.MethodInfos;
using System.Reflection.Runtime.TypeInfos;
using System.Reflection.Runtime.TypeInfos.NativeFormat;

using Internal.Metadata.NativeFormat;
using Internal.Reflection.Core.Execution;
using Internal.Runtime;
using Internal.Runtime.Augments;

using EETypeElementType = Internal.Runtime.EETypeElementType;

namespace Internal.Reflection.Augments
{
    internal static class ReflectionAugments
    {
        internal static unsafe TypeCode GetRuntimeTypeCode(RuntimeType type)
        {
            Debug.Assert(type != null);

            MethodTable* eeType = type.ToMethodTableMayBeNull();
            if (eeType == null)
            {
                // Type exists in metadata only. Aside from the enums, there is no chance a type with a TypeCode would not have an MethodTable,
                // so if it's not an enum, return the default.
                if (!type.IsActualEnum)
                    return TypeCode.Object;
                Type underlyingType = Enum.GetUnderlyingType(type);
                eeType = underlyingType.TypeHandle.ToMethodTable();
            }

            // Note: Type.GetTypeCode() is expected to return the underlying type's TypeCode for enums. EETypePtr.CorElementType does the same,
            // so this one switch handles both cases.
            EETypeElementType rhType = eeType->ElementType;
            switch (rhType)
            {
                case EETypeElementType.Boolean: return TypeCode.Boolean;
                case EETypeElementType.Char: return TypeCode.Char;
                case EETypeElementType.SByte: return TypeCode.SByte;
                case EETypeElementType.Byte: return TypeCode.Byte;
                case EETypeElementType.Int16: return TypeCode.Int16;
                case EETypeElementType.UInt16: return TypeCode.UInt16;
                case EETypeElementType.Int32: return TypeCode.Int32;
                case EETypeElementType.UInt32: return TypeCode.UInt32;
                case EETypeElementType.Int64: return TypeCode.Int64;
                case EETypeElementType.UInt64: return TypeCode.UInt64;
                case EETypeElementType.Single: return TypeCode.Single;
                case EETypeElementType.Double: return TypeCode.Double;
                default:
                    break;
            }

            if (type == typeof(string))
                return TypeCode.String;

            if (type == typeof(DateTime))
                return TypeCode.DateTime;

            if (type == typeof(decimal))
                return TypeCode.Decimal;

            if (type == typeof(DBNull))
                return TypeCode.DBNull;

            return TypeCode.Object;
        }

        public static Assembly Load(AssemblyName assemblyRef, bool throwOnFileNotFound)
        {
            ArgumentNullException.ThrowIfNull(assemblyRef);

            if (throwOnFileNotFound)
                return RuntimeAssemblyInfo.GetRuntimeAssembly(assemblyRef.ToRuntimeAssemblyName());
            else
                return RuntimeAssemblyInfo.GetRuntimeAssemblyIfExists(assemblyRef.ToRuntimeAssemblyName());
        }

        //
        // This overload of GetMethodForHandle only accepts handles for methods declared on non-generic types (the method, however,
        // can be an instance of a generic method.) To resolve handles for methods declared on generic types, you must pass
        // the declaring type explicitly using the two-argument overload of GetMethodFromHandle.
        //
        // This is a vestige from desktop generic sharing that got itself enshrined in the code generated by the C# compiler for Linq Expressions.
        //
        public static MethodBase GetMethodFromHandle(RuntimeMethodHandle runtimeMethodHandle)
        {
            ExecutionEnvironment executionEnvironment = ReflectionCoreExecution.ExecutionEnvironment;
            QMethodDefinition methodHandle;
            RuntimeTypeHandle declaringTypeHandle;
            RuntimeTypeHandle[] genericMethodTypeArgumentHandles;
            if (!executionEnvironment.TryGetMethodFromHandle(runtimeMethodHandle, out declaringTypeHandle, out methodHandle, out genericMethodTypeArgumentHandles))
                throw new ArgumentException(SR.Argument_InvalidHandle);

            MethodBase methodBase = ExecutionDomain.GetMethod(declaringTypeHandle, methodHandle, genericMethodTypeArgumentHandles);
            if (methodBase.DeclaringType.IsConstructedGenericType)  // For compat with desktop, insist that the caller pass us the declaring type to resolve members of generic types.
                throw new ArgumentException(SR.Format(SR.Argument_MethodDeclaringTypeGeneric, methodBase, methodBase.DeclaringType.GetGenericTypeDefinition()));
            return methodBase;
        }

        //
        // This overload of GetMethodHandle can handle all method handles.
        //
        public static MethodBase GetMethodFromHandle(RuntimeMethodHandle runtimeMethodHandle, RuntimeTypeHandle declaringTypeHandle)
        {
            ExecutionEnvironment executionEnvironment = ReflectionCoreExecution.ExecutionEnvironment;
            QMethodDefinition methodHandle;
            RuntimeTypeHandle[] genericMethodTypeArgumentHandles;
            if (!executionEnvironment.TryGetMethodFromHandleAndType(runtimeMethodHandle, declaringTypeHandle, out methodHandle, out genericMethodTypeArgumentHandles))
            {
                // This may be a method declared on a non-generic type: this api accepts that too so try the other table.
                RuntimeTypeHandle actualDeclaringTypeHandle;
                if (!executionEnvironment.TryGetMethodFromHandle(runtimeMethodHandle, out actualDeclaringTypeHandle, out methodHandle, out genericMethodTypeArgumentHandles))
                    throw new ArgumentException(SR.Argument_InvalidHandle);
                if (!actualDeclaringTypeHandle.Equals(declaringTypeHandle))
                    throw new ArgumentException(SR.Format(SR.Argument_ResolveMethodHandle,
                        declaringTypeHandle.GetRuntimeTypeInfoForRuntimeTypeHandle(),
                        actualDeclaringTypeHandle.GetRuntimeTypeInfoForRuntimeTypeHandle()));
            }

            MethodBase methodBase = ExecutionDomain.GetMethod(declaringTypeHandle, methodHandle, genericMethodTypeArgumentHandles);
            return methodBase;
        }

        //
        // This overload of GetFieldForHandle only accepts handles for fields declared on non-generic types. To resolve handles for fields
        // declared on generic types, you must pass the declaring type explicitly using the two-argument overload of GetFieldFromHandle.
        //
        // This is a vestige from desktop generic sharing that got itself enshrined in the code generated by the C# compiler for Linq Expressions.
        //
        public static FieldInfo GetFieldFromHandle(RuntimeFieldHandle runtimeFieldHandle)
        {
            ExecutionEnvironment executionEnvironment = ReflectionCoreExecution.ExecutionEnvironment;
            FieldHandle fieldHandle;
            RuntimeTypeHandle declaringTypeHandle;
            if (!executionEnvironment.TryGetFieldFromHandle(runtimeFieldHandle, out declaringTypeHandle, out fieldHandle))
                throw new ArgumentException(SR.Argument_InvalidHandle);

            FieldInfo fieldInfo = GetFieldInfo(declaringTypeHandle, fieldHandle);
            if (fieldInfo.DeclaringType.IsConstructedGenericType) // For compat with desktop, insist that the caller pass us the declaring type to resolve members of generic types.
                throw new ArgumentException(SR.Format(SR.Argument_FieldDeclaringTypeGeneric, fieldInfo));
            return fieldInfo;
        }

        //
        // This overload of GetFieldHandle can handle all field handles.
        //
        public static FieldInfo GetFieldFromHandle(RuntimeFieldHandle runtimeFieldHandle, RuntimeTypeHandle declaringTypeHandle)
        {
            ExecutionEnvironment executionEnvironment = ReflectionCoreExecution.ExecutionEnvironment;
            FieldHandle fieldHandle;
            if (!executionEnvironment.TryGetFieldFromHandleAndType(runtimeFieldHandle, declaringTypeHandle, out fieldHandle))
            {
                // This may be a field declared on a non-generic type: this api accepts that too so try the other table.
                RuntimeTypeHandle actualDeclaringTypeHandle;
                if (!executionEnvironment.TryGetFieldFromHandle(runtimeFieldHandle, out actualDeclaringTypeHandle, out fieldHandle))
                    throw new ArgumentException(SR.Argument_InvalidHandle);
                if (!actualDeclaringTypeHandle.Equals(declaringTypeHandle))
                    throw new ArgumentException(SR.Format(SR.Argument_ResolveFieldHandle,
                        declaringTypeHandle.GetRuntimeTypeInfoForRuntimeTypeHandle(),
                        actualDeclaringTypeHandle.GetRuntimeTypeInfoForRuntimeTypeHandle()));
            }

            FieldInfo fieldInfo = GetFieldInfo(declaringTypeHandle, fieldHandle);
            return fieldInfo;
        }

        public static EventInfo GetImplicitlyOverriddenBaseClassEvent(EventInfo e)
        {
            return e.GetImplicitlyOverriddenBaseClassMember(EventPolicies.Instance);
        }

        public static MethodInfo GetImplicitlyOverriddenBaseClassMethod(MethodInfo m)
        {
            return m.GetImplicitlyOverriddenBaseClassMember(MethodPolicies.Instance);
        }

        public static PropertyInfo GetImplicitlyOverriddenBaseClassProperty(PropertyInfo p)
        {
            return p.GetImplicitlyOverriddenBaseClassMember(PropertyPolicies.Instance);
        }

        private static RuntimeFieldInfo GetFieldInfo(RuntimeTypeHandle declaringTypeHandle, FieldHandle fieldHandle)
        {
            RuntimeTypeInfo contextTypeInfo = declaringTypeHandle.GetRuntimeTypeInfoForRuntimeTypeHandle();
            NativeFormatRuntimeNamedTypeInfo definingTypeInfo = contextTypeInfo.AnchoringTypeDefinitionForDeclaredMembers.CastToNativeFormatRuntimeNamedTypeInfo();

            // RuntimeFieldHandles always yield FieldInfo's whose ReflectedType equals the DeclaringType.
            RuntimeTypeInfo reflectedType = contextTypeInfo;
            return NativeFormatRuntimeFieldInfo.GetRuntimeFieldInfo(fieldHandle, definingTypeInfo, contextTypeInfo, reflectedType);
        }

        [DebuggerHidden]
        [DebuggerStepThrough]
        public static object ActivatorCreateInstance(
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
            Type type, bool nonPublic)
        {
            return ActivatorImplementation.CreateInstance(type, nonPublic);
        }

        [DebuggerHidden]
        [DebuggerStepThrough]
        public static object ActivatorCreateInstance(
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
            Type type, BindingFlags bindingAttr, Binder? binder, object?[]? args, CultureInfo? culture, object?[]? activationAttributes)
        {
            return ActivatorImplementation.CreateInstance(type, bindingAttr, binder, args, culture, activationAttributes);
        }

        // V2 api: Creates open or closed delegates to static or instance methods - relaxed signature checking allowed.
        public static Delegate CreateDelegate(Type type, object firstArgument, MethodInfo method, bool throwOnBindFailure)
        {
            return CreateDelegateWorker(type, firstArgument, method, throwOnBindFailure, allowClosed: true);
        }

        // V1 api: Creates open delegates to static or instance methods - relaxed signature checking allowed.
        public static Delegate CreateDelegate(Type type, MethodInfo method, bool throwOnBindFailure)
        {
            // This API existed in v1/v1.1 and only expected to create open
            // instance delegates, so we forbid closed delegates for backward compatibility.
            // But we'll allow relaxed signature checking and open static delegates because
            // there's no ambiguity there (the caller would have to explicitly
            // pass us a static method or a method with a non-exact signature
            // and the only change in behavior from v1.1 there is that we won't
            // fail the call).
            return CreateDelegateWorker(type, null, method, throwOnBindFailure, allowClosed: false);
        }

        private static Delegate CreateDelegateWorker(Type type, object firstArgument, MethodInfo method, bool throwOnBindFailure, bool allowClosed)
        {
            ArgumentNullException.ThrowIfNull(type);
            ArgumentNullException.ThrowIfNull(method);

            if (!(type is RuntimeType runtimeDelegateType))
                throw new ArgumentException(SR.Argument_MustBeRuntimeType, nameof(type));

            if (!(method is RuntimeMethodInfo runtimeMethodInfo))
                throw new ArgumentException(SR.Argument_MustBeRuntimeMethodInfo, nameof(method));

            RuntimeTypeInfo runtimeDelegateTypeInfo = runtimeDelegateType.GetRuntimeTypeInfo();

            if (!runtimeDelegateTypeInfo.IsDelegate)
                throw new ArgumentException(SR.Arg_MustBeDelegate, nameof(type));

            Delegate result = runtimeMethodInfo.CreateDelegateNoThrowOnBindFailure(runtimeDelegateTypeInfo, firstArgument, allowClosed);
            if (result == null)
            {
                if (throwOnBindFailure)
                    throw new ArgumentException(SR.Arg_DlgtTargMeth);
                return null;
            }
            return result;
        }

        // V1 api: Creates closed delegates to instance methods only, relaxed signature checking disallowed.
        [RequiresUnreferencedCode("The target method might be removed")]
        public static Delegate CreateDelegate(Type type, object target, string method, bool ignoreCase, bool throwOnBindFailure)
        {
            ArgumentNullException.ThrowIfNull(type);
            ArgumentNullException.ThrowIfNull(target);
            ArgumentNullException.ThrowIfNull(method);

            if (!(type is RuntimeType runtimeDelegateType))
                throw new ArgumentException(SR.Argument_MustBeRuntimeType, nameof(type));

            RuntimeTypeInfo runtimeDelegateTypeInfo = runtimeDelegateType.GetRuntimeTypeInfo();
            if (!runtimeDelegateTypeInfo.IsDelegate)
                throw new ArgumentException(SR.Arg_MustBeDelegate);

            RuntimeTypeInfo runtimeContainingType = target.GetType().ToRuntimeTypeInfo();
            RuntimeMethodInfo runtimeMethodInfo = LookupMethodForCreateDelegate(runtimeDelegateTypeInfo, runtimeContainingType, method, isStatic: false, ignoreCase: ignoreCase);
            if (runtimeMethodInfo == null)
            {
                if (throwOnBindFailure)
                    throw new ArgumentException(SR.Arg_DlgtTargMeth);
                return null;
            }
            return runtimeMethodInfo.CreateDelegateWithoutSignatureValidation(type, target, isStatic: false, isOpen: false);
        }

        // V1 api: Creates open delegates to static methods only, relaxed signature checking disallowed.
        public static Delegate CreateDelegate(Type type, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllMethods)] Type target, string method, bool ignoreCase, bool throwOnBindFailure)
        {
            ArgumentNullException.ThrowIfNull(type);
            ArgumentNullException.ThrowIfNull(target);
            if (target.ContainsGenericParameters)
                throw new ArgumentException(SR.Arg_UnboundGenParam, nameof(target));
            ArgumentNullException.ThrowIfNull(method);

            if (!(type is RuntimeType runtimeDelegateType))
                throw new ArgumentException(SR.Argument_MustBeRuntimeType, nameof(type));

            if (!(target is RuntimeType runtimeContainingType))
                throw new ArgumentException(SR.Argument_MustBeRuntimeType, nameof(target));

            RuntimeTypeInfo runtimeDelegateTypeInfo = runtimeDelegateType.GetRuntimeTypeInfo();

            if (!runtimeDelegateTypeInfo.IsDelegate)
                throw new ArgumentException(SR.Arg_MustBeDelegate);

            RuntimeMethodInfo runtimeMethodInfo = LookupMethodForCreateDelegate(runtimeDelegateTypeInfo, runtimeContainingType.GetRuntimeTypeInfo(), method, isStatic: true, ignoreCase: ignoreCase);
            if (runtimeMethodInfo == null)
            {
                if (throwOnBindFailure)
                    throw new ArgumentException(SR.Arg_DlgtTargMeth);
                return null;
            }
            return runtimeMethodInfo.CreateDelegateWithoutSignatureValidation(type, target: null, isStatic: true, isOpen: true);
        }

        //
        // Helper for the V1/V1.1 Delegate.CreateDelegate() api. These apis take method names rather than MethodInfo and only expect to create open static delegates
        // or closed instance delegates. For backward compatibility, they don't allow relaxed signature matching (which could make the choice of target method ambiguous.)
        //
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
            Justification = "Analysis does not track annotations for RuntimeTypeInfo")]
        private static RuntimeMethodInfo LookupMethodForCreateDelegate(RuntimeTypeInfo runtimeDelegateType, RuntimeTypeInfo containingType, string method, bool isStatic, bool ignoreCase)
        {
            Debug.Assert(runtimeDelegateType.IsDelegate);

            BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.ExactBinding;
            if (isStatic)
            {
                bindingFlags |= BindingFlags.Static;
            }
            else
            {
                bindingFlags |= BindingFlags.Instance | BindingFlags.DeclaredOnly;
            }
            if (ignoreCase)
            {
                bindingFlags |= BindingFlags.IgnoreCase;
            }
            RuntimeMethodInfo invokeMethod = runtimeDelegateType.GetInvokeMethod();
            ReadOnlySpan<ParameterInfo> parameters = invokeMethod.GetParametersAsSpan();
            int numParameters = parameters.Length;
            Type[] parameterTypes = new Type[numParameters];
            for (int i = 0; i < numParameters; i++)
            {
                parameterTypes[i] = parameters[i].ParameterType;
            }

            Type? type = containingType.ToType();
            while (type != null)
            {
                MethodInfo? methodInfo = type.GetMethod(method, 0, bindingFlags, parameterTypes);
                if (methodInfo != null && methodInfo.ReturnType.Equals(invokeMethod.ReturnType))
                    return (RuntimeMethodInfo)methodInfo; // This cast is safe since we already verified that containingType is runtime implemented.

                type = type.BaseType;
            }
            return null;
        }

        public static IntPtr GetFunctionPointer(RuntimeMethodHandle runtimeMethodHandle, RuntimeTypeHandle declaringTypeHandle)
        {
            MethodBase method = GetMethodFromHandle(runtimeMethodHandle, declaringTypeHandle);

            switch (method)
            {
                case RuntimeMethodInfo methodInfo:
                    return methodInfo.LdFtnResult;
                case RuntimeConstructorInfo constructorInfo:
                    return constructorInfo.LdFtnResult;
                default:
                    Debug.Fail("RuntimeMethodHandle should only return a methodbase implemented by the runtime.");
                    throw new NotSupportedException();
            }
        }

        public static void MakeTypedReference(object target, FieldInfo[] flds, out Type type, out int offset)
        {
            ArgumentNullException.ThrowIfNull(target);
            ArgumentNullException.ThrowIfNull(flds);
            if (flds.Length == 0)
                throw new ArgumentException(SR.Arg_ArrayZeroError, nameof(flds));

            offset = 0;
            Type targetType = target.GetType();
            for (int i = 0; i < flds.Length; i++)
            {
                if (!(flds[i] is RuntimeFieldInfo field))
                    throw new ArgumentException(SR.Argument_MustBeRuntimeFieldInfo);
                if (field.IsStatic)
                    throw new ArgumentException(SR.Argument_TypedReferenceInvalidField);

                // For proper handling of Nullable<T> don't change to something like 'IsAssignableFrom'
                // Currently we can't make a TypedReference to fields of Nullable<T>, which is fine.
                Type declaringType = field.DeclaringType;
                if (targetType != declaringType && !targetType.IsSubclassOf(declaringType))
                    throw new MissingMemberException(SR.MissingMemberTypeRef); // MissingMemberException is a strange exception to throw, but it is the compatible exception.

                Type fieldType = field.FieldType;
                if (i < (flds.Length - 1) && !fieldType.IsValueType)
                    throw new MissingMemberException(SR.MissingMemberNestErr); // MissingMemberException is a strange exception to throw, but it is the compatible exception.

                targetType = fieldType;
                offset = checked(offset + field.Offset);
            }

            type = targetType;
        }

        public static Assembly[] GetLoadedAssemblies() => RuntimeAssemblyInfo.GetLoadedAssemblies();

        public static EnumInfo GetEnumInfo(Type type, Func<Type, string[], object[], bool, EnumInfo> create)
        {
            RuntimeTypeInfo runtimeType = type.ToRuntimeTypeInfo();

            var info = runtimeType.GenericCache as EnumInfo;
            if (info != null)
                return info;

            ReflectionCoreExecution.ExecutionEnvironment.GetEnumInfo(runtimeType.TypeHandle, out string[] unsortedNames, out object[] unsortedValues, out bool isFlags);

            // Call into IntrospectiveSort directly to avoid the Comparer<T>.Default codepath.
            // That codepath would bring functionality to compare everything that was ever allocated in the program.
            ArraySortHelper<object, string>.IntrospectiveSort(unsortedValues, unsortedNames, EnumUnderlyingTypeComparer.Instance);

            info = create(RuntimeAugments.GetEnumUnderlyingType(type.TypeHandle), unsortedNames, unsortedValues, isFlags);

            runtimeType.GenericCache = info;
            return info;
        }

        private sealed class EnumUnderlyingTypeComparer : IComparer<object>
        {
            public static readonly EnumUnderlyingTypeComparer Instance = new EnumUnderlyingTypeComparer();

            public int Compare(object? x, object? y)
            {
                Debug.Assert(x is byte or ushort or uint or ulong);
                return x switch
                {
                    byte b => b.CompareTo((byte)y!),
                    ushort us => us.CompareTo((ushort)y!),
                    uint ui => ui.CompareTo((uint)y!),
                    _ => ((ulong)x!).CompareTo((ulong)y!),
                };
            }
        }

        public static DynamicInvokeInfo GetDelegateDynamicInvokeInfo(Type type)
        {
            RuntimeTypeInfo runtimeType = type.ToRuntimeTypeInfo();

            DynamicInvokeInfo? info = runtimeType.GenericCache as DynamicInvokeInfo;
            if (info != null)
                return info;

            RuntimeMethodInfo invokeMethod = runtimeType.GetInvokeMethod();

            MethodBaseInvoker methodInvoker = invokeMethod.MethodInvoker;
            IntPtr invokeThunk = ReflectionCoreExecution.ExecutionEnvironment.GetDynamicInvokeThunk(methodInvoker);

            info = new DynamicInvokeInfo(invokeMethod, invokeThunk);
            runtimeType.GenericCache = info;
            return info;
        }

        public static MethodInfo GetDelegateMethod(Delegate del)
        {
            return ReflectionCoreExecution.ExecutionEnvironment.GetDelegateMethod(del);
        }

        public static MethodBase GetMethodBaseFromStartAddressIfAvailable(IntPtr methodStartAddress)
        {
            return ReflectionCoreExecution.ExecutionEnvironment.GetMethodBaseFromStartAddressIfAvailable(methodStartAddress);
        }

        public static Assembly GetAssemblyForHandle(RuntimeTypeHandle typeHandle)
        {
            return Type.GetTypeFromHandle(typeHandle).Assembly;
        }

        public static void RunClassConstructor(RuntimeTypeHandle typeHandle)
        {
            IntPtr pStaticClassConstructionContext = ReflectionCoreExecution.ExecutionEnvironment.GetStaticClassConstructionContext(typeHandle);
            if (pStaticClassConstructionContext != IntPtr.Zero)
            {
                RuntimeAugments.EnsureClassConstructorRun(pStaticClassConstructionContext);
            }
        }
    }
}
